package flight.services.amf
{
	import flash.net.ObjectEncoding;
	import flash.utils.ByteArray;
	
	public class AMF0Parser 
	{
		public static const AMF0_NUMBER:uint    	        = 0x00;
	    public static const AMF0_BOOLEAN:uint    	       	= 0x01;
	    public static const AMF0_STRING:uint      	      	= 0x02;
	    public static const AMF0_OBJECT:uint      	      	= 0x03;
	    public static const AMF0_MOVIECLIP:uint    	     	= 0x04;
	    public static const AMF0_NULL:uint         	     	= 0x05;
	    public static const AMF0_UNDEFINED:uint    	     	= 0x06;
	    public static const AMF0_REFERENCE:uint         	= 0x07;
	    public static const AMF0_MIXEDARRAY:uint        	= 0x08;
	    public static const AMF0_OBJECTTERM:uint        	= 0x09;
	    public static const AMF0_ARRAY:uint             	= 0x0A;
	    public static const AMF0_DATE:uint              	= 0x0B;
	    public static const AMF0_LONGSTRING:uint        	= 0x0C;
	    public static const AMF0_UNSUPPORTED:uint       	= 0x0E;
	    public static const AMF0_XML:uint           	    = 0x0F;
	    public static const AMF0_TYPEDOBJECT:uint	       	= 0x10;
	   	public static const AMF0_AMF3:uint 					= 0x11;
	   	
	   	// We need a reference to the amf3Parser in order to switch 
	   	// between AMF3 and AMF0 typed objects
		protected var amf3Parser:AMF3Parser;
		
		// These AMF0 types may be referenced by object (as per AMF0 spec)
		// AMF0_OBJECT, AMF0_MIXEDARRAY, AMF0_ARRAY, AMF0_TYPEDOBJECT, AMF0_REFERENCE
		protected var referencedObjects:Array = new Array();
		
		public function AMF0Parser(amf3Parser:AMF3Parser)
		{
			// assign the local reference to the AMF3Parser, which we will 
			// use later.  This is required. 
			this.amf3Parser = amf3Parser;
		}
		
		public function clearAllReferences():void
		{
			referencedObjects = new Array();			
		}
		
		public function determineTypeMarker(data:*):uint
		{
			var typeMarker:uint = AMF0_UNSUPPORTED;
			 
			if (data === null) {
				typeMarker = AMF0_NULL;
			} else if (data is undefined) {
				typeMarker = AMF0_UNDEFINED;
			} else if (data is Number || data is int || data is uint) {
			   	typeMarker = AMF0_NUMBER;
			} else if (data is Boolean) {
				typeMarker = AMF0_BOOLEAN;
			} else if (data is String) {
				typeMarker = AMF0_STRING;
			} else if (data is Array) {
				typeMarker = AMF0_ARRAY;
			} else if (data is Date) {
				typeMarker = AMF0_DATE;
			} else if (data is Object) {
				typeMarker = AMF0_OBJECT;
			}
			
			return typeMarker;
		}
				
		public function readTypeMarkerDataObject(src:ByteArray):Object
		{
			var data:Object = null;
			
			try {
				var typeMarker:uint = src.readByte();
			
				switch (typeMarker) {
					case AMF0_AMF3:
				   		// switch to AMF3 now
				   		src.objectEncoding = ObjectEncoding.AMF3;
				   		if (amf3Parser) {
				   			data = amf3Parser.readTypeMarkerDataObject(src);
				   		}
				   		break;

					case AMF0_NUMBER:	
						data = src.readDouble();
						break;
						
					case AMF0_BOOLEAN:    	   
						data = src.readBoolean();
						break;
						 
				    case AMF0_STRING:
				    	data = src.readUTF();
				    	break;
				    	      	
				    case AMF0_OBJECT:
				    	src.position--;
				    	data = src.readObject();
				    	referencedObjects.push(data);
				    	break;
				          	
				    case AMF0_MOVIECLIP:
				        src.position--;
				        data = src.readObject();
				    	break;
				        	
				    case AMF0_NULL:
				    	data = null;
				    	break;
				    
				    case AMF0_UNDEFINED:   
				    	data = undefined; 	
				    	break;
				    	
				    case AMF0_REFERENCE:
				    	src.position--;
				    	var refIdx:uint = src.readShort();
				    	data = referencedObjects[refIdx];
				    	break;
				    	       
				    case AMF0_MIXEDARRAY:
				    	src.position--;
				    	data = src.readObject();
				    	referencedObjects.push(data);
				    	break;
				          
				    case AMF0_OBJECTTERM:
				    	src.position--;
				    	data = src.readObject();
				    	break;
				    	      
				    case AMF0_ARRAY:          
				    	src.position--;
				    	data = src.readObject();
				    	referencedObjects.push(data);
				    	break;
				    	 
				    case AMF0_DATE:
				    	src.position--;
				    	data = src.readObject();
				    	break;
				    	            
				    case AMF0_LONGSTRING:      
				    	data = src.readUTF();
				    	break;
				    	
				    case AMF0_UNSUPPORTED:
				    	src.position--;	
				    	data = src.readObject();
				    	break;
				    	     
				    case AMF0_XML:       
				    	src.position--;    	
				    	data = src.readObject();
				    	break;
				    	
				    case AMF0_TYPEDOBJECT:
				    	src.position--;
				    	data = src.readObject();
				    	referencedObjects.push(data);
				    	break;	 
				}
			} catch (err:Error) {
				trace("AMFMessage parsing error: " + err.toString());
			}
			
			return data;
		}
		
		public function writeTypeMarkerDataObject(dst:ByteArray, data:Object):void
		{
			var typeMarker:uint = determineTypeMarker(data);
			dst.writeByte(typeMarker);
			
			switch (typeMarker)
			{
				case AMF0_NUMBER:	
					dst.writeDouble(data as Number);
					break;
					
				case AMF0_BOOLEAN:    	   
					dst.writeBoolean(data as Boolean);
					break;
					 
			    case AMF0_STRING:
			    	dst.writeUTF(data as String);
			    	break;
			    	      	
			    case AMF0_OBJECT:
			    	dst.position--;
			    	dst.writeObject(data);
			    	referencedObjects.push(data);
			    	break;
			          	
			    case AMF0_MOVIECLIP:
			    	dst.position--;
			        dst.writeObject(data);
			    	break;
			        	
			    case AMF0_NULL:
			    	break;
			    
			    case AMF0_UNDEFINED:    	
			    	break;
			    	
			    case AMF0_REFERENCE:
			    	dst.writeInt(data as int);
			    	break;
			    	       
			    case AMF0_MIXEDARRAY:
			    	dst.position--;
			    	dst.writeInt(0);
			    	dst.writeObject(data);
			    	referencedObjects.push(data);
			    	break;
			          
			    case AMF0_OBJECTTERM:
			    	dst.position--;
			    	dst.writeObject(data);
			    	break;
			    	      
			    case AMF0_ARRAY:          
			    	dst.position--;
			    	dst.writeObject(data);
			    	referencedObjects.push(data);
			    	break;
			    	 
			    case AMF0_DATE:
			    	dst.position--;
			    	dst.writeObject(data);
			    	break;
			    	            
			    case AMF0_LONGSTRING:      
			    	dst.writeUTF(data as String);
			    	break;
			    	
			    case AMF0_UNSUPPORTED:
			    	dst.position--;
			    	dst.writeObject(data);
			    	break;
			    	     
			    case AMF0_XML:           	
			    	dst.position--;
			    	dst.writeObject(data);
			    	break;
			    	
			    case AMF0_TYPEDOBJECT:
			    	dst.position--;
			    	dst.writeObject(data);
			    	referencedObjects.push(data);
			    	break;	 
			    	   
			   	case AMF0_AMF3:
			   		// switch to AMF3 now
			   		dst.objectEncoding = ObjectEncoding.AMF3;
			   		if (amf3Parser) {
			   			amf3Parser.writeTypeMarkerDataObject(dst, data);
			   		}
			   		break;
			}
		}		
	}
}
